#pragma once

#include <iostream>

#include "types.h"
#include "utils.h"

struct ArithValue {
    Type::Basic type;
    union {
        int i;
        float f;
    } v;

    ArithValue() : type(Type::Basic::Void) {}
    explicit ArithValue(int i) : type(Type::Basic::Int) { v.i = i; }
    explicit ArithValue(float f) : type(Type::Basic::Float) { v.f = f; }
    explicit ArithValue(Type::Basic type) : type(type) { v.i = 0; }
    friend std::ostream& operator<<(std::ostream& out, const ArithValue& t) {
        if (t.type == Type::Basic::Int) out << t.v.i;
        if (t.type == Type::Basic::Float) out << t.v.f;
        out << (char)t.type;
        return out;
    }
};

class ArithOp {
    int op;

  public:
    enum {
        Comma = ',',

        Add = '+',
        Sub = '-',
        Mul = '*',
        Div = '/',
        Mod = '%',

        FloatCast = 'F',
        IntCast = 'I',

        // Pseudo
        Assign = '=',
        Not = '!',
        Less = '<',
        Greater = '>',

        Equal = 'E',         // ==, E
        NotEqual = 'N',      // !=, N
        LessEqual = 'L',     // <=, L
        GreaterEqual = 'G',  // >=, G
        LogicalAnd = '&',    // &&, &
        LogicalOr = '|',     // ||, |
    };

    ArithOp(int tag);

    operator char() const { return (char)op; }
    bool IsUnary() const;
    bool IsBinary() const;

    static ArithValue Calc(ArithValue lhs, ArithValue rhs, ArithOp op);
    static ArithValue Calc(ArithValue rhs, ArithOp op);
    static Type::Basic GetType(Type::Basic operand, ArithOp op);
};
